/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under 
 * the Apache License, Version 2.0  (the "License"); you may not use this file
 * except in compliance with the License.  
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

/*****************************************************************************
  Source      PdnDisconnect.c

  Version     0.1

  Date        2013/05/15

  Product     NAS stack

  Subsystem   EPS Session Management

  Author      Frederic Maurel

  Description Defines the PDN disconnect ESM procedure executed by the
        Non-Access Stratum.

        The PDN disconnect procedure is used by the UE to request
        disconnection from one PDN.

        All EPS bearer contexts established towards this PDN, inclu-
        ding the default EPS bearer context, are released.

*****************************************************************************/
#include <pthread.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

#include "bstrlib.h"

#include "common_types.h"
#include "3gpp_24.007.h"
#include "3gpp_24.008.h"
#include "3gpp_29.274.h"
#include "3gpp_36.401.h"
#include "common_defs.h"
#include "mme_app_ue_context.h"
#include "mme_app_defs.h"
#include "esm_proc.h"
#include "commonDef.h"
#include "log.h"
#include "assertions.h"

#include "esm_data.h"
#include "esm_cause.h"
#include "esm_pt.h"
#include "emm_sap.h"
#include "mme_app_pdn_context.h"

/****************************************************************************/
/****************  E X T E R N A L    D E F I N I T I O N S  ****************/
/****************************************************************************/

extern int _pdn_connectivity_delete (emm_data_context_t * emm_context, pdn_cid_t pid, ebi_t default_ebi);

/****************************************************************************/
/*******************  L O C A L    D E F I N I T I O N S  *******************/
/****************************************************************************/


/*
   --------------------------------------------------------------------------
    Internal data handled by the PDN disconnect procedure in the MME
   --------------------------------------------------------------------------
*/
/*
   PDN disconnection handlers
*/
static int _pdn_disconnect_get_pid (emm_data_context_t * emm_context, ebi_t default_ebi, proc_tid_t pti);


/****************************************************************************/
/******************  E X P O R T E D    F U N C T I O N S  ******************/
/****************************************************************************/



/*
   --------------------------------------------------------------------------
          PDN disconnect procedure executed by the MME
   --------------------------------------------------------------------------
*/
/****************************************************************************
 **                                                                        **
 ** Name:    esm_proc_pdn_disconnect_request()                         **
 **                                                                        **
 ** Description: Performs PDN disconnect procedure requested by the UE.    **
 **                                                                        **
 **              3GPP TS 24.301, section 6.5.2.3                           **
 **      Upon receipt of the PDN DISCONNECT REQUEST message, if it **
 **      is accepted by the network, the MME shall initiate the    **
 **      bearer context deactivation procedure.                    **
 **                                                                        **
 ** Inputs:  ue_id:      UE lower layer identifier                  **
 **      pti:       Identifies the PDN disconnect procedure    **
 **             requested by the UE                        **
 **      Others:    _esm_data                                  **
 **                                                                        **
 ** Outputs:     esm_cause: Cause code returned upon ESM procedure     **
 **             failure                                    **
 **      Return:    The identifier of the PDN connection to be **
 **             released, if it exists;                    **
 **             RETURNerror otherwise.                     **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
int
esm_proc_pdn_disconnect_request (
  emm_data_context_t * emm_context,
  proc_tid_t pti,
  pdn_cid_t  pdn_cid,
  ebi_t default_ebi,
  esm_cause_t *esm_cause)
{

  OAILOG_FUNC_IN (LOG_NAS_ESM);
  pdn_cid_t                               pid = RETURNerror;
  mme_ue_s1ap_id_t                        ue_id = emm_context->ue_id;
  pdn_context_t                          *pdn_context = NULL;

  OAILOG_INFO (LOG_NAS_ESM, "ESM-PROC  - PDN disconnect requested by the UE " "(ue_id=" MME_UE_S1AP_ID_FMT ", default_ebi %d, pti=%d)\n", ue_id, default_ebi, pti);

  /*
   * Get UE's ESM context
   */
//  if (emm_context->esm_ctx.n_active_pdns > 1) {
    /*
     * Get the identifier of the PDN connection entry assigned to the
     * * * * procedure transaction identity
     */
//    pid = _pdn_disconnect_get_pid (emm_context, default_ebi, pti);
  ue_context_t                        *ue_context = mme_ue_context_exists_mme_ue_s1ap_id (&mme_app_desc.mme_ue_contexts, emm_context->ue_id);
  DevAssert(ue_context);

  bearer_context_t * bearer_context_to_deactivate = NULL;
  mme_app_get_session_bearer_context_from_all(ue_context, default_ebi, &bearer_context_to_deactivate); /**< Might be locally removed. */
  if(bearer_context_to_deactivate){
    pdn_cid_t pdn_cx_id = bearer_context_to_deactivate->pdn_cx_id;
    mme_app_get_pdn_context(ue_context, pdn_cx_id, default_ebi, NULL, &pdn_context);
  }else{
    mme_app_get_pdn_context(ue_context, pdn_cid, default_ebi, NULL, &pdn_context);
  }

  if(!pdn_context || pdn_context->context_identifier >= MAX_APN_PER_UE){
    OAILOG_ERROR (LOG_NAS_ESM, "ESM-PROC  - No PDN connection found (pti=%d)\n", pti);
    *esm_cause = ESM_CAUSE_PROTOCOL_ERROR;
    OAILOG_FUNC_RETURN (LOG_NAS_ESM, RETURNerror);
  }
//    return pdn_context->context_identifier;
//  return MAX_APN_PER_UE;
//
//    if (pid >= MAX_APN_PER_UE) {
//      OAILOG_ERROR (LOG_NAS_ESM, "ESM-PROC  - No PDN connection found (pti=%d)\n", pti);
//      *esm_cause = ESM_CAUSE_PROTOCOL_ERROR;
//      OAILOG_FUNC_RETURN (LOG_NAS_ESM, RETURNerror);
//    }
  /*
   * Create a ESM proc data.
   * todo: validate that no ESM Proc is running before starting a new ESM message!
   * Currently, just overwriting the old one.
   */
  if (!emm_context->esm_ctx.esm_proc_data) {
    emm_context->esm_ctx.esm_proc_data  = (esm_proc_data_t *) calloc(1, sizeof(*emm_context->esm_ctx.esm_proc_data));
  }else{
    // todo: don't start a new esm_proc without completing the first one
  }
  emm_context->esm_ctx.esm_proc_data->pti = pti;
  emm_context->esm_ctx.esm_proc_data->pdn_cid = pdn_context->context_identifier;
  emm_context->esm_ctx.esm_proc_data->apn = NULL;
  emm_context->esm_ctx.esm_proc_data->ebi = pdn_context->default_ebi;

  /** Found the PDN context. Informing the MME_APP layer to release the bearers. */
  nas_itti_pdn_disconnect_req(emm_context->ue_id, default_ebi, pdn_context->s_gw_address_s11_s4.address.ipv4_address, pdn_context->s_gw_teid_s11_s4,
      (emm_context->esm_ctx.n_pdns > 1),
      emm_context->esm_ctx.esm_proc_data);
//  } else {
//    /*
//     * Attempt to disconnect from the last PDN disconnection
//     * * * * is not allowed
//     */
//    *esm_cause = ESM_CAUSE_LAST_PDN_DISCONNECTION_NOT_ALLOWED;
//  }

  OAILOG_FUNC_RETURN (LOG_NAS_ESM, RETURNok);
}

/****************************************************************************
 **                                                                        **
 ** Name:    esm_proc_pdn_disconnect_accept()                          **
 **                                                                        **
 ** Description: Performs PDN disconnect procedure accepted by the UE.     **
 **                                                                        **
 **              3GPP TS 24.301, section 6.5.2.3                           **
 **      On reception of DEACTIVATE EPS BEARER CONTEXT ACCEPT mes- **
 **      sage from the UE, the MME releases all the resources re-  **
 **      served for the PDN in the network.                        **
 **                                                                        **
 ** Inputs:  ue_id:      UE lower layer identifier                  **
 **      pid:       Identifier of the PDN connection to be     **
 **             released                                   **
 **      Others:    _esm_data                                  **
 **                                                                        **
 ** Outputs:     esm_cause: Cause code returned upon ESM procedure     **
 **             failure                                    **
 **      Return:    RETURNok, RETURNerror                      **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
int
esm_proc_pdn_disconnect_accept (
  emm_data_context_t * emm_context,
  pdn_cid_t pid,
  ebi_t     default_ebi,
  esm_cause_t *esm_cause)
{
  OAILOG_FUNC_IN (LOG_NAS_ESM);
  mme_ue_s1ap_id_t      ue_id = emm_context->ue_id;
  OAILOG_INFO (LOG_NAS_ESM, "ESM-PROC  - PDN disconnect accepted by the UE " "(ue_id=" MME_UE_S1AP_ID_FMT ", pid=%d)\n", ue_id, pid);
  /*
   * Release the connectivity with the requested PDN
   */
  int                                     rc = mme_api_unsubscribe (NULL);

  if (rc != RETURNerror) {
    /*
     * Delete the PDN connection entry
     */
    proc_tid_t                            pti = _pdn_connectivity_delete (emm_context, pid, default_ebi);

    if (pti != ESM_PT_UNASSIGNED) {
      OAILOG_FUNC_RETURN (LOG_NAS_ESM, RETURNok);
    }
  }

  *esm_cause = ESM_CAUSE_PROTOCOL_ERROR;
  OAILOG_FUNC_RETURN (LOG_NAS_ESM, RETURNerror);
}

/****************************************************************************
 **                                                                        **
 ** Name:    esm_proc_pdn_disconnect_reject()                          **
 **                                                                        **
 ** Description: Performs PDN disconnect procedure not accepted by the     **
 **      network.                                                  **
 **                                                                        **
 **              3GPP TS 24.301, section 6.5.2.4                           **
 **      Upon receipt of the PDN DISCONNECT REQUEST message, if it **
 **      is not accepted by the network, the MME shall send a PDN  **
 **      DISCONNECT REJECT message to the UE.                      **
 **                                                                        **
 ** Inputs:  is_standalone: Not used - Always true                     **
 **      ue_id:      UE lower layer identifier                  **
 **      ebi:       Not used                                   **
 **      msg:       Encoded PDN disconnect reject message to   **
 **             be sent                                    **
 **      ue_triggered:  Not used                                   **
 **      Others:    None                                       **
 **                                                                        **
 ** Outputs:     None                                                      **
 **      Return:    RETURNok, RETURNerror                      **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
int
esm_proc_pdn_disconnect_reject (
  const bool is_standalone,
  emm_data_context_t * emm_context,
  ebi_t ebi,
  STOLEN_REF bstring *msg,
  const bool ue_triggered)
{
  OAILOG_FUNC_IN (LOG_NAS_ESM);
  int                                     rc;
  emm_sap_t                               emm_sap = {0};
  mme_ue_s1ap_id_t                        ue_id = emm_context->ue_id;

  OAILOG_WARNING (LOG_NAS_ESM, "ESM-PROC  - PDN disconnect not accepted by the network " "(ue_id=" MME_UE_S1AP_ID_FMT ")\n", ue_id);
  /*
   * Notity EMM that ESM PDU has to be forwarded to lower layers
   */
  emm_sap.primitive = EMMESM_UNITDATA_REQ;
  emm_sap.u.emm_esm.ue_id = ue_id;
  emm_sap.u.emm_esm.ctx = emm_context;
  emm_sap.u.emm_esm.u.data.msg = *msg;
  *msg = NULL;
  MSC_LOG_TX_MESSAGE (MSC_NAS_ESM_MME, MSC_NAS_EMM_MME, NULL, 0, "EMMESM_UNITDATA_REQ  (PDN DISCONNECT REJECT) ue id " MME_UE_S1AP_ID_FMT " ", ue_id);
  rc = emm_sap_send (&emm_sap);
  OAILOG_FUNC_RETURN (LOG_NAS_ESM, rc);
}

/****************************************************************************/
/*********************  L O C A L    F U N C T I O N S  *********************/
/****************************************************************************/

/*
   --------------------------------------------------------------------------
                Timer handlers
   --------------------------------------------------------------------------
*/


/*
  ---------------------------------------------------------------------------
                PDN disconnection handlers
  ---------------------------------------------------------------------------
*/


/****************************************************************************
 **                                                                        **
 ** Name:    _pdn_disconnect_get_pid()                                 **
 **                                                                        **
 ** Description: Returns the identifier of the PDN connection to which the **
 **      given procedure transaction identity has been assigned    **
 **      to establish connectivity to the specified UE             **
 **                                                                        **
 ** Inputs:  ue_id:      UE local identifier                        **
 **      pti:       The procedure transaction identity         **
 **      Others:    _esm_data                                  **
 **                                                                        **
 ** Outputs:     None                                                      **
 **      Return:    The identifier of the PDN connection if    **
 **             found in the list; -1 otherwise.           **
 **      Others:    None                                       **
 **                                                                        **
 ***************************************************************************/
static pdn_cid_t
_pdn_disconnect_get_pid (
  emm_data_context_t * emm_context,
  ebi_t default_ebi,
  proc_tid_t pti)
{
  if (emm_context) {
    ue_context_t                        *ue_context = mme_ue_context_exists_mme_ue_s1ap_id (&mme_app_desc.mme_ue_contexts, emm_context->ue_id);

    pdn_context_t pdn_context_key = {.default_ebi = default_ebi};

    pdn_context_t * pdn_context = RB_FIND(PdnContexts, &ue_context->pdn_contexts, &pdn_context_key);
    if(pdn_context)
      return pdn_context->context_identifier;
    return MAX_APN_PER_UE;
  }
  return RETURNerror;
}
